﻿#region Using References

using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using Beaker.Services.FileSystem;

#endregion

namespace Beaker.OpenCube.IO
{
	public static class FileSystemExtensions
	{
		/// <summary>
		/// Links two file system services together. If a file cannot be found or opened on the first it will try the fall-back.
		/// </summary>
		/// <param name="main">The main FileSystem</param>
		/// <param name="fallbacks">The fallback FileSystems</param>
		/// <returns>A FileSystemService that will automaticly fallback to the second folder if it was not found on the first folder.</returns>
		public static IFileSystemService Fallback( this IFolder main, params IFolder[] fallbacks )
		{
			Contract.Requires<ArgumentNullException>( main != null );
			Contract.Requires<ArgumentNullException>( fallbacks != null );

			return new FallbackFileSystemService( new[] { main }.Concat( fallbacks ) );
		}

		/// <summary>
		/// Gets the actual parent folder of this file, not scanning for possible fallbacks.
		/// </summary>
		/// <param name="file">The file.</param>
		/// <returns></returns>
		public static IFolder GetActualParentFolder( this IFile file )
		{
			// when it's a fallback entry, return the actual folder instead of the combining folder
			var fallbackFile = file as FallbackFile;
			if( fallbackFile != null )
				return fallbackFile.GetActualParentFolder();

			// not a fallback entry, do normal parent folder
			return file.ParentFolder;
		}
	}

	internal abstract class FallbackEntry
		: IFileSystemEntry
	{
		private readonly FallbackFolder _parent;

		public FallbackEntry( FallbackFolder parent )
		{
			_parent = parent;
		}

		/// <summary>
		/// Gets the name of the entry
		/// </summary>
		public abstract string Name { get; }

		/// <summary>
		/// Get the folder this entry is in
		/// </summary>
		public IFolder ParentFolder
		{
			get { return _parent; }
		}
	}

	internal class FallbackFolder
		: FallbackEntry, IFolder
	{
		private readonly IFolder[] _folders;

		public FallbackFolder( FallbackFolder parent, IEnumerable<IFolder> folders )
			: base( parent )
		{
			Contract.Requires<ArgumentNullException>( folders != null );
			_folders = folders.ToArray();
		}

		public override string Name
		{
			get { return _folders.Select( f => f.Name ).FirstOrDefault(); }
		}

		/// <summary>
		/// Gets the files in this folder.
		/// </summary>
		/// <returns>
		/// A sequence of files.
		/// </returns>
		public IEnumerable<IFile> GetFiles()
		{
			return
				from IFolder folder in _folders
				from IFile file in folder.GetFiles()
				where file != null
				// group by does keep source order. yes!
				// http://stackoverflow.com/questions/1452507/linq-to-objects-does-groupby-preserve-order-of-elements
				// http://msdn.microsoft.com/en-us/library/bb534304.aspx
				group file by file.Name.ToUpperInvariant()
				into fileGroups
				select new FallbackFile( this, fileGroups );
		}

		/// <summary>
		/// Gets the file with the given name.
		/// </summary>
		/// <param name="fileName">The name of the file to get.</param>
		/// <returns>The file with the given name, or <c>null</c> when it could not be found.</returns>
		public IFile GetFile( string fileName )
		{
			return
				new FallbackFile(
					this,
					from IFolder folder in _folders
					where folder != null 
					let file = folder.GetFile( fileName )
					where file != null
					select file
				);
		}

		/// <summary>
		/// Gets the folders in this folder.
		/// </summary>
		/// <returns>
		/// A sequence of folders.
		/// </returns>
		public IEnumerable<IFolder> GetFolders()
		{
			return
				from IFolder folder in _folders
				from IFolder subFolder in folder.GetFolders()
				where subFolder != null
				// group by does keep source order. yes!
				// http://stackoverflow.com/questions/1452507/linq-to-objects-does-groupby-preserve-order-of-elements
				// http://msdn.microsoft.com/en-us/library/bb534304.aspx
				group subFolder by subFolder.Name.ToUpperInvariant()
				into folderGroups
				select new FallbackFolder( this, folderGroups );
		}

		/// <summary>
		/// Gets the folder with the given name.
		/// </summary>
		/// <param name="folderName">The name of the folder to get.</param>
		/// <returns>The folder with the given name, or <c>null</c> when it could not be found.</returns>
		public IFolder GetFolder( string folderName )
		{
			return
				new FallbackFolder(
					this,
					from IFolder folder in _folders
					where folder != null
					select folder.GetFolder( folderName )
				);
		}

		/// <summary>
		/// Gets the entires in this folder.
		/// </summary>
		/// <returns>
		/// A sequence of items.
		/// </returns>
		public IEnumerable<IFileSystemEntry> GetEntries()
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// Gets the entry with the given name in this folder.
		/// </summary>
		/// <param name="entryName">Name of the entry to get.</param>
		/// <returns>The entry with the given name, or <c>null</c> when it could not be found.</returns>
		public IFileSystemEntry GetEntry( string entryName )
		{
			throw new NotImplementedException();
		}
	}

	internal sealed class FallbackFile
		: FallbackEntry, IFile
	{
		private readonly IFile[] _files;

		public FallbackFile( FallbackFolder parent, IEnumerable<IFile> files )
			: base( parent )
		{
			_files = files == null ? new IFile[0] : files.ToArray();
		}

		public override string Name
		{
			get { return _files.Select( f => f.Name ).FirstOrDefault(); }
		}

		internal IFolder GetActualParentFolder()
		{
			return
				_files
					.Select( f => f.ParentFolder )
					.FirstOrDefault();
		}

		/// <summary>
		/// Open the file for reading
		/// </summary>
		/// <returns>A stream of the content of the file</returns>
		public Stream OpenRead()
		{
			return
				_files
					.Select( f => f.OpenRead() )
					.FirstOrDefault();
		}

		/// <summary>
		/// Open the text-file for reading
		/// </summary>
		/// <returns>A text-reader with the content of the file</returns>
		public TextReader OpenText()
		{
			return
				_files
					.Select( f => f.OpenText() )
					.FirstOrDefault();
		}
	}

	internal class FallbackFileSystemService
		: FallbackFolder, IFileSystemService
	{
		public FallbackFileSystemService( params IFolder[] folders )
			: base( null, folders )
		{
		}

		public FallbackFileSystemService( IEnumerable<IFolder> folders )
			: base( null, folders )
		{
		}

		/// <summary>
		/// Parse the given path and return a file or folder for it
		/// </summary>
		/// <param name="path">The path to parse</param>
		/// <returns>A IFileSystemEntry with the file or folder, or <c>null</c> when the file or folder does not exist</returns>
		/// <exception cref="FormatException">Thrown when the path could not be parsed.</exception>
		public IFileSystemEntry ParsePath( string path )
		{
			throw new NotImplementedException();
		}

		/// <summary>
		/// Gets the requested special folder
		/// </summary>
		/// <param name="folder">The special folder to get</param>
		/// <returns>The requested special folder</returns>
		public IFolder GetSpecialFolder( Environment.SpecialFolder folder )
		{
			throw new NotImplementedException();
		}
	}
}
